/*
 * %Z%file      %M%
 * %Z%author    Sun Microsystems, Inc.
 * %Z%version   %I%
 * %Z%date      %D%
 *
 * Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 */


package com.sun.jmx.snmp.agent;



// java imports
//
import java.io.Serializable;
import java.util.Vector;
import java.util.Enumeration;
import java.lang.IllegalAccessException;

// jmx imports
//
import javax.management.ObjectName;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.NotCompliantMBeanException;
import com.sun.jmx.snmp.SnmpOid;
import com.sun.jmx.snmp.SnmpVarBind;
import com.sun.jmx.snmp.SnmpDefinitions;
import com.sun.jmx.snmp.SnmpStatusException;
import com.sun.jmx.snmp.SnmpEngine;
import com.sun.jmx.snmp.SnmpUnknownModelException;
// SNMP Runtime imports
import com.sun.jmx.trace.Trace;

import com.sun.jmx.snmp.internal.SnmpAccessControlModel;
import com.sun.jmx.snmp.internal.SnmpEngineImpl;


/**
 * This list is used in order to construct the OID during the getnext. 
 * The constructed oid is checked by the checker AcmChecker.
 */
final class LongList {

    public static int DEFAULT_CAPACITY = 10;
    
    public static int DEFAULT_INCREMENT = 10;
    

    private final int DELTA;
    private int size;

    /**
     * The list content. Any access to this variable must be protected
     * by a synchronized block on the LongList object.
     * Only read-only action should be performed on this object.
     **/
    public  long[] list;
    
    LongList() {
        this(DEFAULT_CAPACITY,DEFAULT_INCREMENT);
    }
    
    LongList(int initialCapacity) {
        this(initialCapacity,DEFAULT_INCREMENT);
    }
    
    LongList(int initialCapacity, int delta) {
        size = 0; 
        DELTA = delta;
        list = allocate(initialCapacity);
    }
    
    /**
     * Same behaviour than size() in {@link java.util.List}.
     **/
    public final int size() { return size;}
    
    /**
     * Same behaviour than add(long o) in {@link java.util.List}.
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final boolean add(final long o) {
        if (size >= list.length) 
            resize();
        list[size++]=o;
        return true;
    }

    /**
     * Same behaviour than add(int index, long o) in 
     * {@link java.util.List}.
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final void add(final int index, final long o) {
        if (index >  size) throw new IndexOutOfBoundsException();
        if (index >= list.length) resize();
        if (index == size) {
            list[size++]=o;
            return;
        }
	
        java.lang.System.arraycopy(list,index,list,index+1,size-index);
        list[index]=o;
        size++;
    }
   
    /**
     * Adds <var>count</var> elements to the list.
     * @param at index at which the elements must be inserted. The 
     *        first element will be inserted at this index.
     * @param src  An array containing the elements we want to insert.
     * @param from Index of the first element from <var>src</var> that
     *        must be inserted.
     * @param count number of elements to insert.
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final void add(final int at,final long[] src, final int from,
			  final int count) {
	if (count <= 0) return;
	if (at > size) throw new IndexOutOfBoundsException();
	ensure(size+count);
	if (at < size) {
	    java.lang.System.arraycopy(list,at,list,at+count,size-at);
	}
	java.lang.System.arraycopy(src,from,list,at,count);
	size+=count;	
    }

    /**
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final long remove(final int from, final int count) {
        if (count < 1 || from < 0) return -1;
        if (from+count > size) return -1;

        final long o = list[from];
	final int oldsize = size;
	size = size - count;

        if (from == size) return o;
	
        java.lang.System.arraycopy(list,from+count,list,from,
                                   size-from);
        return o;
    }

    /**
     * Same behaviour than remove(int index) in {@link java.util.List}.
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final long remove(final int index) {
        if (index >= size) return -1;
        final long o = list[index];
        list[index]=0;
        if (index == --size) return o;
	
        java.lang.System.arraycopy(list,index+1,list,index,
                                   size-index);
        return o;
    }
    
    /**
     * Same behaviour than the toArray(long[] a) method in
     * {@link java.util.List}.
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final long[] toArray(long[] a) {
        java.lang.System.arraycopy(list,0,a,0,size);
        return a;
    }

    /**
     * Same behaviour than the toArray() method in
     * {@link java.util.List}.
     * Any access to this method should be protected in a synchronized 
     * block on the LongList object.
     **/
    public final long[] toArray() {
        return toArray(new long[size]);
    }
    
    /**
     * Resize the list. Increase its capacity by DELTA elements.
     * Any call to this method must be protected by a synchronized
     * block on this LongList.
     **/
    private final void resize() {
        final long[] newlist = allocate(list.length + DELTA);
        java.lang.System.arraycopy(list,0,newlist,0,size);
        list = newlist;
    }
    
    /**
     * Resize the list. Insure that the new length will be at
     * least equal to <var>length</var>.
     * @param length new minimal length requested.
     * Any call to this method must be protected by a synchronized
     * block on this LongList.
     **/
    private final void ensure(int length) {
	if (list.length < length) {
	    final int min = list.length+DELTA;
	    length=(length<min)?min:length;
	    final long[] newlist = allocate(length);
	    java.lang.System.arraycopy(list,0,newlist,0,size);
	    list = newlist;
	}
    }
    
    /**
     * Allocate a new array of object of specified length.
     **/
    private final long[] allocate(final int length) {
        return new long[length];
    }
    
}

/**
 * Oid Checker makes use of ACM to check each OID during the getnext process.
 */
class AcmChecker {


    SnmpAccessControlModel model = null;
    String principal = null;
    int securityLevel = -1;
    int version = -1;
    int pduType = -1;
    int securityModel = -1;
    byte[] contextName = null;
    SnmpEngineImpl engine = null;
    LongList l = null;
    AcmChecker(SnmpMibRequest req) {
	engine = (SnmpEngineImpl) req.getEngine();
	//We are in V3 architecture, ACM is in the picture.
	if(engine != null) {	    
	    if(engine.isCheckOidActivated()) {
		try {
		    if (isDebugOn()) 
			debug("AcmChecker",
			      " SNMP V3 Access Control to be done.");
		    model = (SnmpAccessControlModel) 
			engine.getAccessControlSubSystem().
			getModel(SnmpDefinitions.snmpVersionThree);
		    principal = req.getPrincipal();
		    securityLevel = req.getSecurityLevel();
		    pduType = req.getPdu().type;
		    version = req.getRequestPduVersion();
		    securityModel = req.getSecurityModel();
		    contextName = req.getAccessContextName();
		    l = new LongList();
		    if (isDebugOn()) 
			debug("AcmChecker",
			      "Will check oid for : principal : " + principal +
			      ";securityLevel : " + 
			      securityLevel +";pduType : " + pduType + 
			      ";version : "
			      + version + ";securityModel : " + 
			      securityModel +";contextName : " + 
			      (contextName == null ? null : 
			       new String(contextName)));
		}catch(SnmpUnknownModelException e) {
		    if (isDebugOn()) 
			debug("AcmChecker", 
			      " Unknown Model, no ACM check.");
		}
	    }
	}
    }
    
    void add(int index, long arc) {
	if(model != null)
	    l.add(index, arc);
    }
    
    void remove(int index) {
	if(model != null)
	    l.remove(index);
    }
    
    void add(final int at,final long[] src, final int from,
	     final int count) {
	if(model != null)
	    l.add(at,src,from,count);
    }

    void remove(final int from, final int count) {
	if(model != null)
	    l.remove(from,count);
    }

    void checkCurrentOid() throws SnmpStatusException {
	if(model != null) {
	    SnmpOid oid = new SnmpOid(l.toArray());
	    if (isDebugOn()) 
		debug("check",
		      " Checking access for : " + oid);
	    model.checkAccess(version,
			      principal,
			      securityLevel,
			      pduType,
			      securityModel,
			      contextName,
			      oid);
	}
    }
    
    // Returns true if debug is on
    private final static boolean isDebugOn() {
        return Trace.isSelected(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP);
    }
    
    // Prints a debug message
    private final static void debug(String func, String info) {
        Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP, 
		   "AcmChecker", func, info);
    }
}

/**
 * Abstract class for representing an SNMP MIB.
 * <P>
 * When compiling a SNMP MIB, among all the classes generated by 
 * <CODE>mibgen</CODE>, there is one which extends <CODE>SnmpMib</CODE> 
 * for representing a whole MIB.
 * <BR>The class is used by the SNMP protocol adaptor as the entry point in 
 * the MIB.
 *
 * <p>This generated class can be subclassed in your code in order to 
 * plug in your own specific behaviour.
 * </p>
 *
 * <p><b>This API is a Sun Microsystems internal API  and is subject 
 * to change without notice.</b></p>
 * @version     %I%     %G%
 * @author      Sun Microsystems, Inc
 */
public abstract class SnmpMib extends SnmpMibAgent implements Serializable {
  
    /**
     * Default constructor.
     * Initializes the OID tree.
     */
    public SnmpMib() {
        root= new SnmpMibOid();
    }


    // --------------------------------------------------------------------
    // POLYMORHIC METHODS
    // --------------------------------------------------------------------

    /**
     * <p>
     * This callback should return the OID associated to the group 
     * identified by the given <code>groupName</code>. 
     * </p>
     *
     * <p>
     * This method is provided as a hook to plug-in some custom
     * specific behavior. Although doing so is discouraged you might
     * want to subclass this method in order to store & provide more metadata
     * information (mapping OID <-> symbolic name) within the agent,
     * or to "change" the root of the MIB OID by prefixing the 
     * defaultOid by an application dependant OID string, for instance.
     * </p>
     *
     * <p>
     * The default implementation of this method is to return the given
     * <code>defaultOid</code>
     * </p>
     *
     * @param groupName   The java-ized name of the SNMP group.
     * @param defaultOid  The OID defined in the MIB for that group 
     *                    (in dot notation).
     *
     * @return The OID of the group identified by <code>groupName</code>,
     *         in dot-notation.
     */
    protected String getGroupOid(String groupName, String defaultOid) {
	return defaultOid;
    }

    /**
     * <p>
     * This callback should return the ObjectName associated to the 
     * group identified by the given <code>groupName</code>. 
     * </p>
     *
     * <p>
     * This method is provided as a hook to plug-in some custom
     * specific behavior. You might want to override this method 
     * in order to provide a different object naming scheme than
     * that proposed by default by <code>mibgen</code>.
     * </p>
     *
     * <p>
     * This method is only meaningful if the MIB is registered
     * in the MBeanServer, otherwise, it will not be called.
     * </p>
     *
     * <p>
     * The default implementation of this method is to return an ObjectName
     * built from the given <code>defaultName</code>.
     * </p>
     *
     * @param name  The java-ized name of the SNMP group.
     * @param oid   The OID returned by getGroupOid() - in dot notation.
     * @param defaultName The name by default generated by <code>
     *                    mibgen</code>
     *
     * @return The ObjectName of the group identified by <code>name</code>
     */
    protected ObjectName getGroupObjectName(String name, String oid, 
					    String defaultName) 
	throws MalformedObjectNameException {
	return new ObjectName(defaultName);
    }

    /**
     * <p>
     * Register an SNMP group and its metadata node in the MIB. 
     * </p>
     *
     * <p>
     * This method is provided as a hook to plug-in some custom
     * specific behavior. You might want to override this method 
     * if you want to set special links between the MBean, its metadata
     * node, its OID or ObjectName etc..
     * </p>
     *
     * <p>
     * If the MIB is not registered in the MBeanServer, the <code>
     * server</code> and <code>groupObjName</code> parameters will be 
     * <code>null</code>.<br>
     * If the given group MBean is not <code>null</code>, and if the 
     * <code>server</code> and <code>groupObjName</code> parameters are 
     * not null, then this method will also automatically register the 
     * group MBean with the given MBeanServer <code>server</code>.
     * </p>
     *
     * @param groupName  The java-ized name of the SNMP group.
     * @param groupOid   The OID as returned by getGroupOid() - in dot 
     *                   notation.
     * @param groupObjName The ObjectName as returned by getGroupObjectName().
     *                   This parameter may be <code>null</code> if the
     *                   MIB is not registered in the MBeanServer.
     * @param node       The metadata node, as returned by the metadata
     *                   factory method for this group.
     * @param group      The MBean for this group, as returned by the
     *                   MBean factory method for this group.
     * @param server     The MBeanServer in which the groups are to be
     *                   registered. This parameter will be <code>null</code>
     *                   if the MIB is not registered, otherwise it is a
     *                   reference to the MBeanServer in which the MIB is
     *                   registered.
     *
     */
    protected void registerGroupNode(String groupName,   String groupOid, 
				     ObjectName groupObjName, SnmpMibNode node,
				     Object group, MBeanServer server)
	throws NotCompliantMBeanException, MBeanRegistrationException, 
	InstanceAlreadyExistsException, IllegalAccessException {
	root.registerNode(groupOid,node);
	if (server != null && groupObjName != null && group != null) 
	    server.registerMBean(group,groupObjName);
    }
 
    /**
     * <p>
     * Register an SNMP Table metadata node in the MIB. 
     * </p>
     *
     * <p>
     * <b><i>
     * This method is used internally and you should never need to 
     * call it directly.</i></b><br> It is used to establish the link 
     * between an SNMP table metadata node and its bean-like counterpart.
     * <br>
     * The group metadata nodes will create and register their 
     * underlying table metadata nodes in the MIB using this 
     * method. <br>
     * The metadata nodes will be later retrieved from the MIB by the
     * bean-like table objects using the getRegisterTableMeta() method.
     * </p>
     *
     * @param name      The java-ized name of the SNMP table.
     * @param table     The SNMP table metadata node - usually this 
     *                  corresponds to a <code>mibgen</code> generated 
     *                  object.
     */
    public abstract void registerTableMeta(String name, SnmpMibTable table);

    /**
     * Returns a registered SNMP Table metadata node.
     *
     * <p><b><i>
     * This method is used internally and you should never need to 
     * call it directly.
     * </i></b></p>
     *
     */
    public abstract SnmpMibTable getRegisteredTableMeta(String name);

    // --------------------------------------------------------------------
    // PUBLIC METHODS
    // --------------------------------------------------------------------
  
    /**
     * Processes a <CODE>get</CODE> operation.
     *
     **/ 
    // Implements the method defined in SnmpMibAgent. See SnmpMibAgent
    // for java-doc
    //
    public void get(SnmpMibRequest req) throws SnmpStatusException {

	// Builds the request tree: creation is not allowed, operation
	// is not atomic.

	final int reqType = SnmpDefinitions.pduGetRequestPdu;
	SnmpRequestTree handlers = getHandlers(req,false,false,reqType);

	SnmpRequestTree.Handler h = null;
	SnmpMibNode meta = null;

	if (isDebugOn()) 
	    debug("get","Processing handlers for GET... ");

	// For each sub-request stored in the request-tree, invoke the
	// get() method.
	for (Enumeration eh=handlers.getHandlers();eh.hasMoreElements();) {
	    h = (SnmpRequestTree.Handler) eh.nextElement();

	    // Gets the Meta node. It can be either a Group Meta or a 
	    // Table Meta.
	    //
	    meta = handlers.getMetaNode(h);

	    // Gets the depth of the Meta node in the OID tree
	    final int depth = handlers.getOidDepth(h);

	    for (Enumeration rqs=handlers.getSubRequests(h);
		 rqs.hasMoreElements();) {

		// Invoke the get() operation.
		meta.get((SnmpMibSubRequest)rqs.nextElement(),depth);
	    }
	}
    }

    /**
     * Processes a <CODE>set</CODE> operation.
     * 
     */
    // Implements the method defined in SnmpMibAgent. See SnmpMibAgent
    // for java-doc
    //
    public void set(SnmpMibRequest req) throws SnmpStatusException {

	SnmpRequestTree handlers = null;

	// Optimization: we're going to get the whole SnmpRequestTree
	// built in the "check" method, so that we don't have to rebuild 
	// it here.
	//
	if (req instanceof SnmpMibRequestImpl)
	    handlers = ((SnmpMibRequestImpl)req).getRequestTree();

	// Optimization didn't work: we have to rebuild the tree.
	//
	// Builds the request tree: creation is not allowed, operation
	// is atomic.
	//
	final int reqType = SnmpDefinitions.pduSetRequestPdu;
	if (handlers == null) handlers = getHandlers(req,false,true,reqType);
	handlers.switchCreationFlag(false);
	handlers.setPduType(reqType);

	SnmpRequestTree.Handler h = null;
	SnmpMibNode meta = null;

	if (isDebugOn()) 
	    debug("set","Processing handlers for SET... ");

	// For each sub-request stored in the request-tree, invoke the
	// get() method.
	for (Enumeration eh=handlers.getHandlers();eh.hasMoreElements();) {
	    h = (SnmpRequestTree.Handler) eh.nextElement();

	    // Gets the Meta node. It can be either a Group Meta or a 
	    // Table Meta.
	    //
	    meta = handlers.getMetaNode(h);

	    // Gets the depth of the Meta node in the OID tree
	    final int depth = handlers.getOidDepth(h);

	    for (Enumeration rqs=handlers.getSubRequests(h);
		 rqs.hasMoreElements();) {

		// Invoke the set() operation
		meta.set((SnmpMibSubRequest)rqs.nextElement(),depth);
	    }
	}
    }

    /**
     * Checks if a <CODE>set</CODE> operation can be performed.
     * If the operation cannot be performed, the method will raise a 
     * <CODE>SnmpStatusException</CODE>.
     *
     */
    // Implements the method defined in SnmpMibAgent. See SnmpMibAgent
    // for java-doc
    //
    public void check(SnmpMibRequest req) throws SnmpStatusException {

	final int reqType = SnmpDefinitions.pduWalkRequest;
	// Builds the request tree: creation is allowed, operation
	// is atomic.
	SnmpRequestTree handlers = getHandlers(req,true,true,reqType);

	SnmpRequestTree.Handler h = null;
	SnmpMibNode meta = null;

	if (isDebugOn()) 
	    debug("check","Processing handlers for CHECK... ");

	// For each sub-request stored in the request-tree, invoke the
	// check() method.
	for (Enumeration eh=handlers.getHandlers();eh.hasMoreElements();) {
	    h = (SnmpRequestTree.Handler) eh.nextElement();

	    // Gets the Meta node. It can be either a Group Meta or a 
	    // Table Meta.
	    //
	    meta = handlers.getMetaNode(h);

	    // Gets the depth of the Meta node in the OID tree
	    final int depth = handlers.getOidDepth(h);

	    for (Enumeration rqs=handlers.getSubRequests(h);
		 rqs.hasMoreElements();) {

		// Invoke the check() operation
		meta.check((SnmpMibSubRequest)rqs.nextElement(),depth);
	    }
	}

	// Optimization: we're going to pass the whole SnmpRequestTree
	// to the "set" method, so that we don't have to rebuild it there.
	//
	if (req instanceof SnmpMibRequestImpl) {
	    ((SnmpMibRequestImpl)req).setRequestTree(handlers);
	}

    }

    /**
     * Processes a <CODE>getNext</CODE> operation.
     * 
     */
    // Implements the method defined in SnmpMibAgent. See SnmpMibAgent
    // for java-doc
    //
    public void getNext(SnmpMibRequest req) throws SnmpStatusException {
	// Build the request tree for the operation
	// The subrequest stored in the request tree are valid GET requests
	SnmpRequestTree handlers = getGetNextHandlers(req);

	SnmpRequestTree.Handler h = null;
	SnmpMibNode meta = null;

	if (isDebugOn()) 
	    debug("getNext","Processing handlers for GET-NEXT... ");

	// Now invoke get() for each subrequest of the request tree.
	for (Enumeration eh=handlers.getHandlers();eh.hasMoreElements();) {
	    h = (SnmpRequestTree.Handler) eh.nextElement();
	    
	    // Gets the Meta node. It can be either a Group Meta or a 
	    // Table Meta.
	    //
	    meta = handlers.getMetaNode(h);

	    // Gets the depth of the Meta node in the OID tree
	    int depth = handlers.getOidDepth(h);

	    for (Enumeration rqs=handlers.getSubRequests(h);
		 rqs.hasMoreElements();) {

		// Invoke the get() operation
		meta.get((SnmpMibSubRequest)rqs.nextElement(),depth);
	    }
	}
    }

    
    /**
     * Processes a <CODE>getBulk</CODE> operation.
     * The method implements the <CODE>getBulk</CODE> operation by calling 
     * appropriately the <CODE>getNext</CODE> method.
     *
     */
    // Implements the method defined in SnmpMibAgent. See SnmpMibAgent
    // for java-doc
    //
    public void getBulk(SnmpMibRequest req, int nonRepeat, int maxRepeat) 
	throws SnmpStatusException {
     
        getBulkWithGetNext(req, nonRepeat, maxRepeat);
    }
    
    /**
     * Gets the root object identifier of the MIB.
     * <P>In order to be accurate, the method should be called once the 
     * MIB is fully initialized (that is, after a call to <CODE>init</CODE>
     * or <CODE>preRegister</CODE>).
     *
     * @return The root object identifier.
     */
    public long[] getRootOid() {
        
        if( rootOid == null) {
            Vector list= new Vector(10);

            // Ask the tree to do the job !
            //
            root.getRootOid(list);

            // Now format the result
            //
            rootOid= new long[list.size()];
            int i=0;
            for(Enumeration e= list.elements(); e.hasMoreElements(); ) {
                Integer val= (Integer) e.nextElement();
                rootOid[i++]= val.longValue();
            }
        }
        return rootOid;

    }
  
    // --------------------------------------------------------------------
    // PRIVATE METHODS
    //---------------------------------------------------------------------

    /**
     * This method builds the temporary request-tree that will be used to
     * perform the SNMP request associated with the given vector of varbinds
     * `list'. 
     *
     * @param req The SnmpMibRequest object holding the varbind list 
     *             concerning this MIB.
     * @param createflag Indicates whether the operation allow for creation
     *        of new instances (ie: it is a SET).
     * @param atomic Indicates whether the operation is atomic or not.
     * @param type Request type (from SnmpDefinitions).
     *
     * @return The request-tree where the original varbind list has been
     *         dispatched to the appropriate nodes.
     */
    private SnmpRequestTree getHandlers(SnmpMibRequest req, 
					boolean createflag, boolean atomic,
					int type) 
	throws SnmpStatusException {

	// Build an empty request tree
	SnmpRequestTree handlers = 
	    new SnmpRequestTree(req,createflag,type);

        int index=0;
        SnmpVarBind var = null;
        final int ver= req.getVersion();

	// For each varbind in the list finds its handling node.
        for (Enumeration e= req.getElements(); e.hasMoreElements(); index++) {

            var= (SnmpVarBind) e.nextElement();

            try {
		// Find the handling node for this varbind.
                root.findHandlingNode(var,var.oid.longValue(false),
				      0,handlers);
            } catch(SnmpStatusException x) {

		if (isDebugOn()) 
		    debug("getHandlers","Couldn't find a handling node for "
			  + var.oid.toString());

		// If the operation is atomic (Check/Set) or the version
		// is V1 we must generate an exception.
		//
                if (ver == SnmpDefinitions.snmpVersionOne) {

		    if (isDebugOn()) 
			debug("getHandlers","\tV1: Throwing exception");

		    // The index in the exception must correspond to the 
		    // SNMP index ...
		    //
		    final SnmpStatusException sse = 
			new SnmpStatusException(x, index + 1);
		    sse.initCause(x);
		    throw sse;
		} else if ((type == SnmpDefinitions.pduWalkRequest)   ||
			   (type == SnmpDefinitions.pduSetRequestPdu)) {
		    final int status = 
			SnmpRequestTree.mapSetException(x.getStatus(),ver);

 		    if (isDebugOn()) 
			debug("getHandlers","\tSET: Throwing exception");

		    final SnmpStatusException sse = 
			new SnmpStatusException(status, index + 1);
		    sse.initCause(x);
		    throw sse;
		} else if (atomic) {

		    // Should never come here...
 		    if (isDebugOn()) 
			debug("getHandlers","\tATOMIC: Throwing exception");

		    final SnmpStatusException sse = 
			new SnmpStatusException(x, index + 1);
		    sse.initCause(x);
		    throw sse;
		}

		final int status = 
		    SnmpRequestTree.mapGetException(x.getStatus(),ver);
		    
		if (status == SnmpStatusException.noSuchInstance) {

		    if (isDebugOn()) 
			debug("getHandlers", 
			      "\tGET: Registering noSuchInstance");

		    var.value= SnmpVarBind.noSuchInstance;

		} else if (status == SnmpStatusException.noSuchObject) {
			
		    if (isDebugOn()) 
			debug("getHandlers",
			      "\tGET: Registering noSuchObject");

			var.value= SnmpVarBind.noSuchObject;

		} else {

		    if (isDebugOn()) 
			debug("getHandlers",
			      "\tGET: Registering global error: "
			      + status);

		    final SnmpStatusException sse = 
			new SnmpStatusException(status, index + 1);
		    sse.initCause(x);
		    throw sse;
		}
	    }
        }
	return handlers;
    }

    /**
     * This method builds the temporary request-tree that will be used to
     * perform the SNMP GET-NEXT request associated with the given vector 
     * of varbinds `list'. 
     *
     * @param req The SnmpMibRequest object holding the varbind list 
     *             concerning this MIB.
     *
     * @return The request-tree where the original varbind list has been
     *         dispatched to the appropriate nodes, and where the original
     *         OIDs have been replaced with the correct "next" OID.
     */
    private SnmpRequestTree getGetNextHandlers(SnmpMibRequest req)
	throws SnmpStatusException {

	// Creates an empty request tree, no entry creation is allowed (false)
	SnmpRequestTree handlers = new 
	    SnmpRequestTree(req,false,SnmpDefinitions.pduGetNextRequestPdu);

	// Sets the getNext flag: if version=V2, status exception are 
	// transformed in  endOfMibView
	handlers.setGetNextFlag();

	if (isDebugOn()) 
	    debug("getGetNextHandlers","Received MIB request : " + req);
	AcmChecker checker = new AcmChecker(req);
	int index=0;
        SnmpVarBind var = null;
        final int ver= req.getVersion();
	SnmpOid original = null;
	// For each varbind, finds the handling node.
	// This function has the side effect of transforming a GET-NEXT
	// request into a valid GET request, replacing the OIDs in the
	// original GET-NEXT request with the OID of the first leaf that
	// follows.
        for (Enumeration e= req.getElements(); e.hasMoreElements(); index++) {

            var = (SnmpVarBind) e.nextElement();
	    SnmpOid result = null;
            try {
		// Find the node handling the OID that follows the varbind
		// OID. `result' contains this next leaf OID.
		//ACM loop.
		if (isDebugOn()) 
		    debug("getGetNextHandlers"," Next Oid of :" + var.oid);
		result = new SnmpOid(root.findNextHandlingNode
				     (var,var.oid.longValue(false),0,
				      0,handlers, checker));
		
		if (isDebugOn()) 
		    debug("getGetNextHandlers"," is :" + result);
		// We replace the varbind original OID with the OID of the
		// leaf object we have to return.
		var.oid = result;
            } catch(SnmpStatusException x) {

		// if (isDebugOn()) 
		//    debug("getGetNextHandlers",
		//	  "Couldn't find a handling node for "
		//	  + var.oid.toString());
		
                if (ver == SnmpDefinitions.snmpVersionOne) {
		    if (isDebugOn()) 
			debug("getGetNextHandlers","\tThrowing exception" + 
			      x.toString());
		    // The index in the exception must correspond to the 
		    // SNMP index ...
		    //
                    throw new SnmpStatusException(x, index + 1);
		}
		if (isDebugOn()) 
		    debug("getGetNextHandlers","Exception : " + x.getStatus());
		
                var.setSnmpValue(SnmpVarBind.endOfMibView);
            }   
        }
	return handlers;
    }
    
    // Returns true if debug is on
    private final static boolean isDebugOn() {
        return Trace.isSelected(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP);
    }

    // Prints a debug message
    private final static void debug(String func, String info) {
        Trace.send(Trace.LEVEL_DEBUG, Trace.INFO_ADAPTOR_SNMP, 
		   "SnmpMib", func, info);
    }

    // --------------------------------------------------------------------
    // PROTECTED VARIABLES
    // --------------------------------------------------------------------

    /**
     * The top element in the Mib tree.
     * @serial
     */
    protected SnmpMibOid root;

  
    // --------------------------------------------------------------------
    // PRIVATE VARIABLES
    // --------------------------------------------------------------------

    /**
     * The root object identifier of the MIB.
     */
    private transient long[] rootOid= null;
} 
